package top.archiesean.sqlSession.impl;

import top.archiesean.config.BoundSql;
import top.archiesean.pojo.Configuration;
import top.archiesean.pojo.MappedStatement;
import top.archiesean.sqlSession.Executor;
import top.archiesean.utils.GenericTokenParser;
import top.archiesean.utils.ParameterMapping;
import top.archiesean.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * 执行器
 *
 * @author ArchieSean
 * @create 2023/7/11
 */
public class SimpleExecutor implements Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
        try {
            //1.注册驱动，获取连接
            Connection connection = configuration.getDataSource().getConnection();
            //2.获取sql
            String sql = mappedStatement.getSql();
            //3.sql转换 将#{} 转换为 ?
            BoundSql boundSql = getBoundSql(sql);
            //4.获取预处理对象
            PreparedStatement statement = connection.prepareStatement(boundSql.getSqlText());
            //5.设置参数
            List<ParameterMapping> mappingList = boundSql.getParameterMappingList();
            String parameterType = mappedStatement.getParameterType();
            //获取参数的class对象
            Class<?> clazz = getClassType(parameterType);
            for (int i = 0; i < mappingList.size(); i++) {
                ParameterMapping parameterMapping = mappingList.get(i);
                String content = parameterMapping.getContent();

                //反射
                Field decaredField = clazz.getDeclaredField(content);
                decaredField.setAccessible(true);
                //参数对应的值
                Object value = decaredField.get(params[0]);
                statement.setObject(i + 1, value);

            }
            //6. 执行sql
            ResultSet resultSet = statement.executeQuery();
            //7.封装返回值结果集
            Class<?> resultClazz = getClassType(mappedStatement.getResultType());
            ArrayList<Object> list = new ArrayList<>();
            while (resultSet.next()) {
                //获取元数据
                ResultSetMetaData metaData = resultSet.getMetaData();
                //返回的结果集对象
                Object resultObj = resultClazz.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    //字段名
                    String columnName = metaData.getColumnName(i);
                    //对应的值
                    Object columnValue = resultSet.getObject(columnName);
                    //使用反射或者内省，根据数据库表和实体的对应关系，完成封装
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClazz);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(resultObj, columnValue);
                }
                list.add(resultObj);
            }
            statement.close();
            connection.close();
            return (List<E>) list;
        } catch (Exception e) {
            throw new RuntimeException("获取sql连接失败");
        }
    }

    /**
     * 根据参数类型获取参数类型字节码
     *
     * @param parameterType 入参路径
     * @return class
     */
    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType != null) {
            return Class.forName(parameterType);
        }
        return null;
    }

    /**
     * 完成对#{}的解析工作： 1.将#{}使用？代替,2: 解析出#{}里边的属性
     *
     * @return sql sql语句
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类： 配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        //解析出来的sql
        String parseSql = tokenParser.parse(sql);
        //#{}里边解析出来的参数名称
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        return new BoundSql(parseSql, parameterMappings);
    }
}
